package wsz.sqlSession;

import wsz.cache.Cache;
import wsz.cache.CacheKey;
import wsz.cache.PerpetualCache;
import wsz.config.BoundSql;
import wsz.mapper.RowBounds;
import wsz.pojo.Configuration;
import wsz.pojo.MappedStatement;
import wsz.utils.GenericTokenParser;
import wsz.utils.ParameterMapping;
import wsz.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

/**
 * 真正的jdbc执行
 * @author wsz
 * @date 2021/10/1 8:15
 **/
public class DefaultExecutor implements Executor {

    private PerpetualCache localCache;

    public DefaultExecutor() {
        localCache = new PerpetualCache("LocalCache");
    }

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement statement, Object... params) throws Exception {
        // 1.注册驱动，获取链接
        Connection connection = configuration.getDataSource().getConnection();

        // 2.获取sql：select * from user where id=#{} and username = #{}
        String sql = statement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        CacheKey cacheKey = createCacheKey(statement, params, new RowBounds(), boundSql);

        // FIXME 从二级缓存中获取数据
        Cache secondCache = statement.getCache();
        List<E> mappedCache = (List<E>) secondCache.getObject(cacheKey);
        if (mappedCache != null) {
            System.out.println("从二级缓存中获取数据");
            return mappedCache;
        }

        // FIXME 从一级缓存sqlSession中获取
        List<E> list = (List<E>) localCache.getObject(cacheKey);
        if (list != null) {
            System.out.println("从一级缓存中直接获取数据");
            return list;
        }

        // 3.获取预处理对象
        PreparedStatement prepareStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4.设置参数
        String paramterType = statement.getParamterType();
        Class<?> paramterClass = getClassType(paramterType);
        if (paramterClass != null) {
            List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
            for (int i = 0; i < parameterMappingList.size(); i++) {
                ParameterMapping parameterMapping = parameterMappingList.get(i);
                String content = parameterMapping.getContent();

                // 使用反射
                Field field = paramterClass.getDeclaredField(content);
                field.setAccessible(true);
                Object o = field.get(params[0]);

                prepareStatement.setObject(i+1, o);
            }
        } else {
            if (params != null) {
                for (int i = 0; i < params.length; i++) {
                    prepareStatement.setObject(i+1, params[i]);
                }
            }
        }
        // 5.执行sql
        System.out.println("准备执行sql:" + boundSql.getSqlText());
        ResultSet resultSet = prepareStatement.executeQuery();

        // 6.封装返回结果集
        String resultType = statement.getResultType();
        Class<?> resultClass = getClassType(resultType);

        ArrayList<Object> objects = new ArrayList<>();
        while (resultSet.next()) {
            // 封装后的对象实体类
            Object instance = resultClass.newInstance();

            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount() ; i++) {
                String columnName = metaData.getColumnName(i);
                Object object = resultSet.getObject(columnName);

                // 使用反射/内省，封装，库表与实体类字段的对象
                PropertyDescriptor descriptor = new PropertyDescriptor(columnName, resultClass);
                Method writeMethod = descriptor.getWriteMethod();
                writeMethod.invoke(instance, object);
            }

            objects.add(instance);
        }
        List<E> eList = (List<E>) objects;

        // FIXME 补充到一级缓存中
        secondCache.putObject(cacheKey, eList);
        localCache.putObject(cacheKey, eList);
        return eList;
    }

    @Override
    public int update(Configuration configuration, MappedStatement statement, Object... params) throws Exception {
        // 1.注册驱动，获取链接
        Connection connection = configuration.getDataSource().getConnection();

        // 2.获取/转换sql
        String sql = statement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        // 3.获取预处理对象
        PreparedStatement prepareStatement = connection.prepareStatement(boundSql.getSqlText());

        String paramterType = statement.getParamterType();
        Class<?> paramterClass = getClassType(paramterType);
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();

            // 使用反射
            Field field = paramterClass.getDeclaredField(content);
            field.setAccessible(true);
            Object o = field.get(params[0]);

            prepareStatement.setObject(i+1, o);
        }

        // FIXME 更新后，清空缓存，直接全部清除
        clearMapppedStatement(configuration);
        localCache.clear();
        return prepareStatement.executeUpdate();
    }

    /**
     * 暂时全部清除缓存
     * @param configuration
     */
    private void clearMapppedStatement(Configuration configuration) {
        configuration.getStatementMap().forEach((id, map) -> map.getCache().clear());
    }

    @Override
    public int delete(Configuration configuration, MappedStatement statement, Object... params) throws Exception {
        // 1.注册驱动，获取链接
        Connection connection = configuration.getDataSource().getConnection();

        // 2.获取/转换sql
        String sql = statement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        // 3.获取预处理对象
        PreparedStatement prepareStatement = connection.prepareStatement(boundSql.getSqlText());

        String paramterType = statement.getParamterType();
        Class<?> paramterClass = getClassType(paramterType);
        if (paramterClass != null) {
            List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
            for (int i = 0; i < parameterMappingList.size(); i++) {
                ParameterMapping parameterMapping = parameterMappingList.get(i);
                String content = parameterMapping.getContent();

                // 使用反射
                Field field = paramterClass.getDeclaredField(content);
                field.setAccessible(true);
                Object o = field.get(params[0]);

                prepareStatement.setObject(i+1, o);
            }
        } else {
            for (int i = 0; i < params.length; i++) {
                prepareStatement.setObject(i+1, params[i]);
            }
        }

        return prepareStatement.executeUpdate();
    }

    private Class<?> getClassType(String className) {
        if (className == null) {
            return null;
        }
        try {
//            if ("int".equals(className)) {
//                return Integer.class;
//            }
            return Class.forName(className);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * 完成#{}的解析
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        // 标记处理类
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser tokenParser = new GenericTokenParser("#{", "}", tokenHandler);
        // 解析后的sql
        String parseSql = tokenParser.parse(sql);
        // #{}里面的参数名称
        List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();

        return new BoundSql(parseSql, parameterMappings);
    }

    /**
     * 生成缓存的key
     * @param statement
     * @param params
     * @param rowBounds
     * @param boundSql
     * @return
     */
    private CacheKey createCacheKey(MappedStatement statement, Object[] params, RowBounds rowBounds, BoundSql boundSql) {
        CacheKey cacheKey = new CacheKey();
        cacheKey.setLimit(rowBounds.getLimit());
        cacheKey.setOffset(rowBounds.getOffset());
        cacheKey.setSqlText(boundSql.getSqlText());
        cacheKey.setStatementId(statement.getId());

        cacheKey.update(statement.getId());
        cacheKey.update(rowBounds.getOffset());
        cacheKey.update(rowBounds.getLimit());
        cacheKey.update(boundSql.getSqlText());

        return cacheKey;
    }
}
